package com.bayair.doctor.widget;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewManager;
import android.view.ViewParent;
import android.view.animation.OvershootInterpolator;

import com.bayair.doctor.BuildConfig;
import com.bayair.doctor.R;


/**
 * A simple view class that will display an enlarging icon animation. For best results the provided icon should have a
 * solid, mono-color background with a transparent hole where the icon's silhouette is supposed to be
 *
 * @author yildizkabaran
 */
public class SplashView extends View {

    private static final String TAG = "SplashView";

    /**
     * A simple interface to listen to the state of the splash animation
     *
     * @author yildizkabaran
     */
    public interface ISplashListener {
        void onStart();

        void onUpdate(float completionFraction);

        void onEnd();
    }

    /**
     * Context constructor
     *
     * @param context
     */
    public SplashView(Context context) {
        super(context);
        initialize();
    }

    /**
     * Context and attributes constructor
     *
     * @param context
     * @param attrs
     */
    public SplashView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
        setupAttributes(attrs);
    }

    /**
     * Context, attributes, and style constructor
     *
     * @param context
     * @param attrs
     */
    public SplashView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
        setupAttributes(attrs);
    }

    public static final int DEFAULT_HOLE_FILL_COLOR = Color.WHITE;
    public static final int DEFAULT_ICON_COLOR = Color.rgb(23, 169, 229);
    public static final int DEFAULT_DURATION = 500;
    public static final boolean DEFAULT_REMOVE_FROM_PARENT_ON_END = true;

    private static final int PAINT_STROKE_WIDTH = 2; // give a stroke width to the paint so that the rectangles get a little overlap

    private Drawable mIcon; // most important item, cannot be null
    private int mHoleFillColor = DEFAULT_HOLE_FILL_COLOR; // color to be shown in the transparent hole before the animation starts
    private int mIconColor = DEFAULT_ICON_COLOR; // should be the same color of as the icon background
    private long mDuration = DEFAULT_DURATION; // total duration, in ms, of the animation
    private boolean mRemoveFromParentOnEnd = true; // a flag for removing the view from its parent once the animation is over
    private float mCurrentScale = 1; // used for keeping track of how far along the animation we are

    // cache some dimension values to make the onDraw method simpler looking
    private int mWidth, mHeight;
    private int mIconWidth, mIconHeight;
    private float mMaxScale = 1;

    // cache the paint object so that it doesn't need to be allocated in onDraw
    private Paint mPaint = new Paint();

    /**
     * Setup custom attributes from XML
     *
     * @param attrs
     */
    private void setupAttributes(AttributeSet attrs) {
        Context context = getContext();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SplashView);

        int numAttrs = a.getIndexCount();
        for (int i = 0; i < numAttrs; ++i) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.SplashView_splashIcon:
                    setIconDrawable(a.getDrawable(i));
                    break;
                case R.styleable.SplashView_iconColor:
                    setIconColor(a.getColor(i, DEFAULT_ICON_COLOR));
                    break;
                case R.styleable.SplashView_holeFillColor:
                    setHoleFillColor(a.getColor(i, DEFAULT_HOLE_FILL_COLOR));
                    break;
                case R.styleable.SplashView_duration:
                    setDuration(a.getInt(i, DEFAULT_DURATION));
                    break;
                case R.styleable.SplashView_removeFromParentOnEnd:
                    setRemoveFromParentOnEnd(a.getBoolean(i, DEFAULT_REMOVE_FROM_PARENT_ON_END));
                    break;
            }
        }
        a.recycle();
    }

    /**
     * Initialized the view properties. No much is done in this method since most variables already have set defaults
     */
    private void initialize() {
        // make the background transparent so that the view does not automatically draw any unwanted colors
        setBackgroundColor(Color.TRANSPARENT);

        // set fill style on the paint so that the rectangles get filled
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeWidth(PAINT_STROKE_WIDTH);
    }

    /**
     * Set the fill color of the view that will be seen through the transparent hole of the icon before the animation starts
     */
    public void setHoleFillColor(int bgColor) {
        mHoleFillColor = bgColor;
    }

    /**
     * Set the color of the icon. This value will be used to draw 4 rectangles around the icon to fill the entire view.
     * If not set, or set incorrectly the edges of the icon image will be visible. There are a few tricks to make this animation
     * look right, and this is one of them. Make sure this color is set correctly.
     *
     * @param iconColor
     */
    public void setIconColor(int iconColor) {
        mIconColor = iconColor;
    }

    /**
     * Set the duration of the entire animation in milliseconds.
     *
     * @param duration
     */
    public void setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("duration cannot be less than 0");
        }

        mDuration = duration;
    }

    /**
     * Set the resource id of the Drawable to be used as the icon. See setIconDrawable(Drawable) for more details.
     *
     * @param resId
     */
    public void setIconResource(int resId) {
        Drawable icon = getResources().getDrawable(resId);

        if (icon == null) {
            throw new IllegalArgumentException("no drawable found for the resId: " + resId);
        }

        setIconDrawable(icon);
    }

    /**
     * Set the Drawable to be used as the icon. It can be any kind of Drawable that has an intrinsic width and height.
     * So far changing the size of the Drawable is not supported but can be added in the future
     *
     * @param icon
     */
    public void setIconDrawable(Drawable icon) {
        mIcon = icon;
        if (mIcon != null) {
            mIconWidth = mIcon.getIntrinsicWidth();
            mIconHeight = mIcon.getIntrinsicHeight();
            // set the bounds of the drawable to its own dimensions
            // canvas scaling will be used to change the bounds of the icon
            Rect iconBounds = new Rect();
            iconBounds.left = 0;
            iconBounds.top = 0;
            iconBounds.right = mIconWidth;
            iconBounds.bottom = mIconHeight;
            mIcon.setBounds(iconBounds);
        } else {
            mIconWidth = 0;
            mIconHeight = 0;
        }

        setMaxScale();
    }

    /**
     * Set the flag to remove or keep the view after the animation is over. This is set to true by default. The view must be inside a ViewManager
     * (or ViewParent) for this to work. Otherwise, the view will not be removed and a warning log will be produced.
     *
     * @param shouldRemove
     */
    public void setRemoveFromParentOnEnd(boolean shouldRemove) {
        mRemoveFromParentOnEnd = shouldRemove;
    }

    /**
     * A helper method for determining for large the icon should be enlarged before the animation ends. There is a chance that the entire view will not become
     * transparent by the end of the animation.
     */
    private void setMaxScale() {
        if (mIconWidth < 1 || mIconHeight < 1) {
            mMaxScale = 1;
            return;
        }

        mMaxScale = 2 * Math.max((float) mWidth / mIconWidth, (float) mHeight / mIconHeight);

        // just to make sure the animation does not actually work backwards
        if (mMaxScale < 1) {
            mMaxScale = 1;
        }
    }

    /**
     * Starts the splash and disappear animation. If a listener is provided it will notify the listener on animation events
     *
     * @param listener
     */
    public void splashAndDisappear(final ISplashListener listener) {
        // create an animator from scale 1 to max
        final ValueAnimator animator = ValueAnimator.ofFloat(1, mMaxScale);
        // set the duration
        animator.setDuration(mDuration);
        // set an overshoot interpolator with a low tension value so that the icon becomes a little smaller before it expands
        animator.setInterpolator(new OvershootInterpolator(1F));

        // add an update listener so that we draw the view on each update
        animator.addUpdateListener(new AnimatorUpdateListener() {
            @SuppressLint("NewApi")
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // keep in mind that the animation runs in reverse to get the desired effect from the interpolator
                // therefore we need to subtract to correct for this effect, and then add 1 so that the scale doesn't dip below 0
                // this is NOT fool-proof, and therefore can be made better
                mCurrentScale = 1 + mMaxScale - (Float) animation.getAnimatedValue();

                // invalidate the view so that it gets redraw if it needs to be
                invalidate();

                // notify the listener if set
                // for some reason this animation can run beyond 100%
                if (listener != null) {
                    listener.onUpdate((float) animation.getCurrentPlayTime() / mDuration);
                }
            }
        });

        // add a listener for the general animation events, use the AnimatorListenerAdapter so that we don't clutter the code
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                // notify the listener of animation start (if listener is set)
                if (listener != null) {
                    listener.onStart();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // check if we need to remove the view on animation end
                if (mRemoveFromParentOnEnd) {
                    // get the view parent
                    ViewParent parent = getParent();
                    // check if a parent exists and that it implements the ViewManager interface
                    if (parent != null && parent instanceof ViewManager) {
                        ViewManager viewManager = (ViewManager) parent;
                        // remove the view from its parent
                        viewManager.removeView(SplashView.this);
                    } else if (BuildConfig.DEBUG) {
                        // even though we had to remove the view we either don't have a parent, or the parent does not implement the method
                        // necessary to remove the view, therefore create a warning log (but only do this if we are in DEBUG mode)
                        Log.w(TAG, "splash view not removed after animation ended because no ViewManager parent was found");
                    }
                }

                // notify the listener of animation end (if listener is set)
                if (listener != null) {
                    listener.onEnd();
                }
            }
        });

        // start the animation using post so that the animation does not start if the view is not in foreground
        post(new Runnable() {
            @Override
            public void run() {
                // start the animation in reverse to get the desired effect from the interpolator
                animator.reverse();
            }
        });
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // do whatever the super wants to do
        super.onSizeChanged(w, h, oldw, oldh);

        // cache the width and height for easy access
        mWidth = w;
        mHeight = h;

        // re-set the max scale because the size has changed
        setMaxScale();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // calculate the scaled width and height
        float iconWidth = mIconWidth * mCurrentScale;
        float iconHeight = mIconHeight * mCurrentScale;

        // calculate all corners of the icon rectangle with the icon in the middle
        float mIconLeft = (mWidth - iconWidth) / 2;
        float mIconRight = mIconLeft + iconWidth;
        float mIconTop = (mHeight - iconHeight) / 2;
        float mIconBottom = mIconTop + iconHeight;

        // if the scale is less than 2, then don't enable the transparent hole yet
        if (mCurrentScale < 2) {
            // draw a bgColored rectangle right underneath the icon, make the rectangle a little bigger using the threshold value
            mPaint.setColor(mHoleFillColor);
            canvas.drawRect(mIconLeft, mIconTop, mIconRight, mIconBottom, mPaint);
        }

        // draw 4 rectangles around the icon to cover the entire screen, use threshold value to expand and overlap the rectangles
        mPaint.setColor(mIconColor);
        canvas.drawRect(0, 0, mIconLeft, mHeight, mPaint);
        canvas.drawRect(mIconLeft, 0, mIconRight, mIconTop, mPaint);
        canvas.drawRect(mIconLeft, mIconBottom, mIconRight, mHeight, mPaint);
        canvas.drawRect(mIconRight, 0, mWidth, mHeight, mPaint);

        if (mIcon != null) {
            // save the current canvas state
            canvas.save();
            // translate the canvas to draw the icon
            canvas.translate(mIconLeft, mIconTop);
            // scale the canvas for the desired icon scale
            canvas.scale(mCurrentScale, mCurrentScale);
            // draw the icon on the canvas
            mIcon.draw(canvas);
            // restore the canvas to its original state
            canvas.restore();
        } else if (BuildConfig.DEBUG) {
            // if the icon is not set then log a warning message if we are in debug mode, this message will be logged every time onDraw is called
            Log.w(TAG, "icon is not set when the view needs to be drawn");
        }
    }
}
